﻿#region imports

using System;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;

#endregion

namespace Codeplex.ODataTFS.Model.ExpressionVisitors
{
	public class BuildFilterExpressionVisitor : ExpressionVisitor
	{
		private Expression _expression;
		private FilterNode _rootFilterNode;

		public BuildFilterExpressionVisitor(Expression expression)
		{
			_expression = expression;
		}

		public FilterNode Eval()
		{
			if (_expression == null)
			{
				return _rootFilterNode;
			}

			Visit(_expression);

			return _rootFilterNode;
		}

		protected override Expression VisitBinary(BinaryExpression node)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			var allowedProperties = new[] {"Project", "Definition", "Number", "Reason", "Quality", "Status", "RequestedBy", "RequestedFor", "StartTime", "FinishTime", "BuildFinished"};

			if (node.NodeType == ExpressionType.OrElse)
			{
				throw new NotSupportedException("Logical OR operators are not supported for Build Custom filters");
			}

			if (node.Left is MemberExpression && node.Right is ConstantExpression)
			{
				var fieldName = ((MemberExpression) node.Left).Member.Name;
				var value = ((ConstantExpression) node.Right).Value;

				if (!allowedProperties.ToList().Contains(fieldName))
				{
					throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "You can only filter by the following properties: {0}. (e.g. /Builds/$filter=Number gt 100 and  Quality eq 'Healthy') ", string.Join(", ", allowedProperties)));
				}

				AddFilterNode(fieldName, value, FilterNode.ParseFilterExpressionType(node.NodeType), FilterNodeRelationship.And);
			}
			else if (node.Left.NodeType == ExpressionType.Conditional)
			{
				throw new NotSupportedException("Only equality and inequality operators between fields and constant expressions are allowed with Build Custom filters");
			}

			return base.VisitBinary(node);
		}

		private void AddFilterNode(string fieldName, object value, FilterExpressionType expressionType, FilterNodeRelationship filterNodeRelationship)
		{
			if (_rootFilterNode != null && _rootFilterNode.SingleOrDefault(p => p.Key.Equals(fieldName, StringComparison.OrdinalIgnoreCase)) != null)
			{
				throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "More than one filter expression found for attribute {0}. Only one filter expression per attribute is supported.", fieldName));
			}

			var filterNode = new FilterNode {Key = fieldName, Value = (value == null) ? "null" : value.ToString(), Sign = expressionType, NodeRelationship = filterNodeRelationship};
			if (_rootFilterNode != null)
			{
				_rootFilterNode.AddNode(filterNode);
			}
			else
			{
				_rootFilterNode = filterNode;
			}
		}
	}
}
